package net.scratch.netty.util;
/**
 * 
 * @author 'Mystic Flow
 * @author `Discardedx2
 */
public class PacketBuffer {

	private static final int[] BIT_MASK = new int[32];

	static {
		for (int i = 0; i < 32; i++) {
			BIT_MASK[i] = (1 << i) - 1;
		}
	}

	public static enum PacketSize {
		STANDARD(0), VAR_BYTE(1), VAR_SHORT(2);

		private final int size;

		private PacketSize(int size) {
			this.size = size;
		}

		public int size() {
			return size;
		}
	}

	private static final int DEFAULT_SIZE = Byte.MAX_VALUE;

	private byte[] buffer = new byte[DEFAULT_SIZE];
	private int position = 0;
	private int bitPosition;

	private final int opcode;
	private final PacketSize size;

	public static int getDEFAULT_SIZE() {
		return DEFAULT_SIZE;
	}

	public PacketSize getSize() {
		return size;
	}

	public PacketBuffer() {
		this.opcode = -1;
		this.size = PacketSize.STANDARD;
	}

	public PacketBuffer(int opcode) {
		this.opcode = opcode;
		this.size = PacketSize.STANDARD;
	}

	public PacketBuffer(int opcode, PacketSize size) {
		this.opcode = opcode;
		this.size = size;
	}

	public PacketBuffer put(final byte value) {
		ensure(position + 1);
		buffer[position++] = value;
		return this;
	}

	public PacketBuffer putByteC(final int value) {
		ensure(position + 1);
		buffer[position++] = (byte) -value;
		return this;
	}

	public PacketBuffer putByteA(int value) {
		ensure(position + 1);
		buffer[position++] = (byte) (value + 128);
		return this;
	}
	public PacketBuffer put128Byte(int value) {
		ensure(position + 1);
		buffer[position++] = (byte) (value - 128);
		return this;
	}
	public PacketBuffer putByteS(int value) {
		ensure(position + 1);
		buffer[position++] = (byte) (128 - value);
		return this;
	}


	public PacketBuffer put(final byte[] data) {
		ensure(position + data.length);
		System.arraycopy(data, 0, buffer, position, data.length);
		position += data.length;
		return this;
	}

	public PacketBuffer putBytes(final byte[] b, final int offset, final int length) {
		ensure(position + (length - offset));
		System.arraycopy(b, offset, buffer, position, length);
		position += length - offset;
		return this;
	}

	public PacketBuffer putReverseBytes(final byte[] b) {
		ensure(position + b.length);
		for (int i = b.length - 1; i >= 0; i--) {
			put((byte) (b[i]));
		}
		return this;
	}
	
	public void putReverseBytes(byte[] b, int offset, int length) {
		ensure(position + b.length);
		for (int i = length- 1; i >= offset; i--) {
			put((byte) (b[i]));
		}
	}

	public PacketBuffer putRS2String(final String s) {
		ensure(s.length() + 1);
		return put(s.getBytes()).put((byte) 0);
	}

	public PacketBuffer putShort(final int value) {
		ensure(position + 2);
		buffer[position++] = (byte) (value >> 8);
		buffer[position++] = (byte) value;
		return this;
	}

	public PacketBuffer putShortA(final int value) {
		ensure(position + 2);
		buffer[position++] = (byte) (value >> 8);
		buffer[position++] = (byte) (value + 128);
		return this;
	}

	public PacketBuffer putLEShortA(final int value) {
		ensure(position + 2);
		buffer[position++] = (byte) (value + 128);
		buffer[position++] = (byte) (value >> 8);
		return this;
	}

	public PacketBuffer putLEShort(final int value) {
		ensure(position + 2);
		buffer[position++] = (byte) value;
		buffer[position++] = (byte) (value >> 8);
		return this;
	}

	public PacketBuffer putInt(final int value) {
		ensure(position + 4);
		buffer[position++] = (byte) (value >> 24);
		buffer[position++] = (byte) (value >> 16);
		buffer[position++] = (byte) (value >> 8);
		buffer[position++] = (byte) value;
		return this;
	}

	public PacketBuffer putInt1(final int val) {
		ensure(position + 4);
		buffer[position++] = ((byte) (val >> 8));
		buffer[position++] = ((byte) val);
		buffer[position++] = ((byte) (val >> 24));
		buffer[position++] = ((byte) (val >> 16));
		return this;
	}

	public PacketBuffer putInt2(final int val) {
		ensure(position + 4);
		buffer[position++] = ((byte) (val >> 16));
		buffer[position++] = ((byte) (val >> 24));
		buffer[position++] = ((byte) val);
		buffer[position++] = ((byte) (val >> 8));
		return this;
	}

	public PacketBuffer putLEInt(final int val) {
		ensure(position + 4);
		buffer[position++] = (byte) (val);
		buffer[position++] = (byte) (val >> 8);
		buffer[position++] = (byte) (val >> 16);
		buffer[position++] = (byte) (val >> 24);
		return this;
	}

	public PacketBuffer putLong(final long value) {
		ensure(position + 8);
		buffer[position++] = (byte) (value >> 56);
		buffer[position++] = (byte) (value >> 48);
		buffer[position++] = (byte) (value >> 40);
		buffer[position++] = (byte) (value >> 32);
		buffer[position++] = (byte) (value >> 24);
		buffer[position++] = (byte) (value >> 16);
		buffer[position++] = (byte) (value >> 8);
		buffer[position++] = (byte) value;
		return this;
	}

	public PacketBuffer putSmart(final int value) {
		if (value < 128) {
			return put((byte) value);
		}
		return putShort((short) value);
	}

	public PacketBuffer putJAGString(final String value) {
		ensure(value.length() + 2);
		return put((byte) 0).put(value.getBytes()).put((byte) 0);
	}

	public Packet toPacket() {
		final byte[] packetBuffer = new byte[position];
		System.arraycopy(buffer, 0, packetBuffer, 0, position);
		return new Packet(opcode, size, packetBuffer);
	}

	public void ensure(final int newCapacity) {
		if (newCapacity >= buffer.length) {
			final byte[] newBuffer = new byte[newCapacity + 100];
			System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
			buffer = newBuffer;
		}
	}

	public void initBitAccess() {
		bitPosition = position * 8;
	}

	public void finishBitAccess() {
		position = (bitPosition + 7) / 8;
	}

	public PacketBuffer putBits(int numBits, final int value) {
		int bytePos = bitPosition >> 3;
		int bitOffset = 8 - (bitPosition & 7);
		bitPosition += numBits;
		position = (bitPosition + 7) / 8;
		ensure(position);
		for (; numBits > bitOffset; bitOffset = 8) {
			buffer[bytePos] &= ~ BIT_MASK[bitOffset];
			buffer[bytePos++] |= (value >> (numBits - bitOffset)) & BIT_MASK[bitOffset];
			numBits -= bitOffset;
		}
		if (numBits == bitOffset) {
			buffer[bytePos] &= ~ BIT_MASK[bitOffset];
			buffer[bytePos] |= value & BIT_MASK[bitOffset];
		} else {
			buffer[bytePos] &= ~ (BIT_MASK[numBits] << (bitOffset - numBits));
			buffer[bytePos] |= (value & BIT_MASK[numBits]) << (bitOffset - numBits);
		}
		return this;
	}

	public int position() {
		return position;
	}

	public void putBytesA(final byte[] data, final int length) {
		ensure(position + data.length);
		for(int i = 0; i < length; i ++) {
			buffer[position++] = (byte) (data[i] + 128);
		}
	}
	
	public void putBytesA(byte[] data, int offset, int length) {
		ensure(position + data.length);
		for(int i = offset; i < length; i++) {
			buffer[position++] = (byte) (data[i] + 128);
		}
	}

	public byte[] getBuffer() {
		return buffer;
	}

	public int getPosition() {
		return position;
	}

	public int getOpcode() {
		return opcode;
	}




}
